package com.cby.code.util;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;


import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author ChenBaoyang
 * @description: 处理手机厂商，在toast前面会增加应用名的问题（通过hook的方式进行移除toast文案上的应用名）
 * @date 7/20/21 09:30
 */
public class HookToastUtils {


    /**
     * 通过反射的方式获取Toast的sServce对象
     *
     * <p>
     * Class<Toast> toastClass = Toast.class;
     * Field sServiceField = toastClass.getDeclaredField("sService");
     * sServiceField.setAccessible(true);
     * </p>
     *
     * <p>
     * 前面已经获取到了sService的Field，它是静态的，那直接通过sServiceField.get(null)获取不就可以了？
     * 然而并不能获取到，这是因为整个Hook操作是在应用初始化时，整个应用还没有执行过Toast.show()的操作，
     * 因此sService还没有初始化（因为它是一个懒汉单例）。
     * 所以修改成下面的方式进行去获取，提前调用<h4>getService</h4> 方法
     * </p>
     */
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public static void hookToastService(Context context){
        try {
            Class<Toast> toastClass = Toast.class;
            Method getServiceMethod = toastClass.getDeclaredMethod("getService", (Class<?>) null);
            getServiceMethod.setAccessible(true);
            Object service = getServiceMethod.invoke(null);
            hookToastContent(context, service);
        }catch (NoSuchMethodException
                | InvocationTargetException
                | IllegalAccessException
                | NoSuchFieldException
                | ClassNotFoundException e) {
            CbyLogUtils.flog(e.getMessage());
        }
    }

    /**
     * 动态代理，进行hook
     *
     * @param context
     * @param service
     */
    @SuppressLint("SoonBlockedPrivateApi")
    private static void hookToastContent(Context context, Object service) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        //无法直接引用到这个类
        Class<?> clz = Class.forName("android.app.INotificationManager");
        Object proxy = Proxy.newProxyInstance(Toast.class.getClassLoader(), new Class[]{ clz }, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                // 判断enqueueToast()方法时执行操作
                if ("enqueueToast".equals(method.getName())) {
                    CbyLogUtils.flog("hook", method.getName());
                    //进行文件移除
                    getContent(context, args[1]);
                }
                return method.invoke(service, args);
            }
        });
        Class<Toast> toastClass = Toast.class;
        Field sServiceField = toastClass.getDeclaredField("sService");
        sServiceField.setAccessible(true);
        // 用代理对象给sService赋值
        sServiceField.set(null, proxy);
    }

    /**
     * 进行文本中的应用名字去除（小米的toast会有）
     * @param context
     * @param arg
     */
    private static void getContent(Context context, Object arg) {
        try {
            // 获取TN的class
            Class<?> tnClass = Class.forName(Toast.class.getName() + "$TN");
            // 获取mNextView的Field
            Field mNextViewField = tnClass.getDeclaredField("mNextView");
            mNextViewField.setAccessible(true);
            // 获取mNextView实例
            LinearLayout mNextView = (LinearLayout) mNextViewField.get(arg);
            // 获取TextView
            TextView childView = (TextView) mNextView.getChildAt(0);
            // 获取文本内容
            CharSequence text = childView.getText();
            // 替换文本并赋值
            String target = AppUtils.getAppName(context) + "：";
            childView.setText(text.toString().replace(target, ""));
            CbyLogUtils.flog("hook", "content: " + childView.getText());
        } catch (ClassNotFoundException e) {
            CbyLogUtils.flog(e.getMessage());
        } catch (IllegalAccessException e) {
            CbyLogUtils.flog(e.getMessage());
        } catch (NoSuchFieldException e) {
            CbyLogUtils.flog(e.getMessage());
        }
    }
}
